﻿using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

namespace DB.SchemaSync.Library.Models
{
	public class Column : DbObject
	{
		[JsonIgnore]
		public Table Table { get; set; }

        /// <summary>
        /// 列名
        /// </summary>
		public string Name { get; set; }

        /// <summary>
        /// 列类型
        /// </summary>
		public string DataType { get; set; }

        /// <summary>
        /// 是否未空
        /// </summary>
		public bool IsNullable { get; set; }

        /// <summary>
        /// 默认值
        /// </summary>
		public string Default { get; set; }

        /// <summary>
        /// 字符集
        /// </summary>
		public string Collation { get; set; }

        /// <summary>
        /// 宽度
        /// </summary>
		public long MaxLength { get; set; }
		public int Scale { get; set; }
		public int Precision { get; set; }

        /// <summary>
        /// 默认值约束名称
        /// </summary>
        public string DefName { get; set; }

        /// <summary>
        /// 字段描述
        /// </summary>
        public string Description { get; set; }
        

        /// <summary>
        /// Expression used for computed column
        /// </summary>
        public string Expression { get; set; }

		/// <summary>
		/// Expression to use with UPDATE...SET when creating required column in table with data
		/// </summary>
		public string DefaultExpression { get; set; }

		public override IEnumerable<string> AlterCommands(SqlSyntax syntax, Database database)
		{
			if (!string.IsNullOrEmpty(AlterDescription)) yield return $"{syntax.CommentStart} {AlterDescription}";

			var dependencies = GetDependencies(database);
			foreach (var dep in dependencies)
			{
				foreach (var cmd in dep.DropCommands(syntax)) yield return cmd;
			}

			yield return $"ALTER TABLE <{Table}> ALTER COLUMN {Definition(syntax,isAlter:true)}";

            foreach (var dep in dependencies)
			{
				foreach (var cmd in dep.CreateCommands(syntax)) yield return cmd;
			}
		}

		public override IEnumerable<string> CreateCommands(SqlSyntax syntax)
		{
			if (!Table.IsEmpty && !IsNullable && !string.IsNullOrEmpty(DefaultExpression))
			{
				yield return $"ALTER TABLE <{Table}> ADD {Definition(syntax, true)}";
				yield return $"UPDATE <{Table}> SET <{Name}>={DefaultExpression}";
				yield return $"ALTER TABLE <{Table}> ALTER COLUMN {Definition(syntax)}";
			}
			else
			{
				yield return $"ALTER TABLE <{Table}> ADD {Definition(syntax)}";
			}
		}

		public override IEnumerable<string> DropCommands(SqlSyntax syntax)
		{
			yield return $"ALTER TABLE <{Table}> DROP COLUMN <{Name}>";
		}

		public override string ToString()
		{
			return $"{Table}.{Name}";
		}

		public override bool Equals(object obj)
		{
			Column col = obj as Column;
			if (col != null)
			{
				return Table.Equals(col.Table) && (Name ?? string.Empty).ToLower().Equals(col.Name.ToLower());
			}
			return false;
		}


		/// <summary>
		/// 获取列定义
		/// </summary>
		/// <param name="syntax"></param>
		/// <param name="nullableOverride"></param>
		/// <returns></returns>
        internal string Definition(SqlSyntax syntax, bool? nullableOverride = null, bool isAlter = false)
        {
            string result = null;
            string identity = string.Empty;

			//如果是sqlserver并且是在变更的列情况，无法直接执行 alter xtb alter column xcol int identity(1,1)
			if (isAlter && syntax.BatchSeparator  == "GO")
            {
                identity = string.Empty;
			}
            else
            {
				//自增列定义
				identity = (Table.IdentityColumn?.Equals(Name) ?? false) ? $" {syntax.IdentitySyntax[DataType]}" : string.Empty;
			}

			if (!string.IsNullOrEmpty(Expression))
            {
                result = $"<{Name}> AS {Expression}";
            }
            else
            {
                bool isNull = (!nullableOverride.HasValue) ? IsNullable : nullableOverride.Value;
                
                result = $"<{Name}> {syntax.GetDataTypeDefinition(this)} {identity} {((isNull) ? "NULL" : "NOT NULL")}";
                if (!string.IsNullOrEmpty(Default)) result += $" DEFAULT ({Default})";
            }
            return result;
        }


		//internal string Definition(SqlSyntax syntax, bool? nullableOverride = null)
		//{
		//	string result = null;

		//	if (!string.IsNullOrEmpty(Expression))
		//	{
		//		result = $"<{Name}> AS {Expression}";
		//	}
		//	else
		//	{
		//		bool isNull = (!nullableOverride.HasValue) ? IsNullable : nullableOverride.Value;
		//		string identity = (Table.IdentityColumn?.Equals(Name) ?? false) ? $" {syntax.IdentitySyntax[DataType]}" : string.Empty;
		//		result = $"<{Name}> {syntax.GetDataTypeDefinition(this)} {identity} {((isNull) ? "NULL" : "NOT NULL")}";
		//		if (!string.IsNullOrEmpty(Default)) result += $" DEFAULT ({Default})";
		//	}

		//	return result;
		//}

		public override int GetHashCode()
		{
			return Table.GetHashCode() + (Name ?? string.Empty).ToLower().GetHashCode();
		}

		public override bool IsAltered(DbObject compare)
		{
			Column test = compare as Column;
			if (test != null)
			{
				if (!(test?.Expression ?? string.Empty).Equals(Expression ?? string.Empty))
				{
					AlterDescription = $"Calculation expression changed from {test.Expression} to {Expression}";
					return true;
				}

				if (DataType.Equals("decimal") && string.IsNullOrEmpty(Expression))
				{
					if (test.Scale != Scale)
					{
						AlterDescription = $"Scale changed from {test.Scale} to {Scale}";
						return true;
					}

					if (test.Precision != Precision)
					{
						AlterDescription = $"Precision changed from {test.Precision} to {Precision}";
						return true;
					}
				}

				if (!test.DataType.Equals(DataType))
				{
					AlterDescription = $"Data type changed from {test.DataType} to {DataType}";
					return true;
				}

				if (DataType.StartsWith("var") || DataType.StartsWith("nvar"))
				{
					if (test.MaxLength != MaxLength)
					{
						AlterDescription = $"Max length changed from {test.MaxLength} to {MaxLength}";
						return true;
					}
				}

				if (test.IsNullable != IsNullable && string.IsNullOrEmpty(Expression))
				{
					AlterDescription = $"Nullable changed from {test.IsNullable} to {IsNullable}";
					return true;
				}

				if (!(test?.Collation ?? string.Empty).Equals(Collation ?? test?.Collation ?? string.Empty))
				{
					AlterDescription = $"Collation changed from {test.Collation} to {Collation}";
					return true;
				}

				// not sure how to handle defaults as they don't really impact the column def
			}

			return false;
		}

		public bool IsForeignKey(Database database, out ForeignKey foreignKey)
		{
			foreignKey = database.ForeignKeys.SingleOrDefault(fk =>
				fk.ReferencingTable.Equals(Table) &&
				fk.Columns.Any(col => col.ReferencingName.Equals(Name)));
			return (foreignKey != null);
		}

		public override ExcludeObject GetExcludeObject()
		{
			return new ExcludeObject() { Type = "Column", Name = ToString() };
		}

		public override bool IsValid(ActionType action, out string message)
		{
			if (DataType.Equals("decimal") && Scale == 0)
			{
				message = "Decimal columns must have a scale and precision set.";
				return false;
			}

			if (action == ActionType.Create)
			{
				if (!Table.IsEmpty && !IsNullable && string.IsNullOrEmpty(DefaultExpression) && !Name.Equals(Table.IdentityColumn))
				{
					message = "Adding a required column to a table with data requires the [DefaultExpression] attribute.";
					return false;
				}
			}

			return base.IsValid(action, out message);
		}

		public override IEnumerable<DbObject> GetDependencies(Database database)
		{
			foreach (var ndx in Table.Indexes.Where(ndx => ndx.ContainsColumn(this)))
			{
				yield return ndx;
			}

			foreach (var fk in database.ForeignKeys.Where(fk => fk.Columns.Any(col => col.ReferencingName.Equals(this.Name) && fk.ReferencingTable.Equals(this.Table))))
			{
				yield return fk;
			}
		}



	}
}